<?php
// +----------------------------------------------------------------------
// | 杭州十禾信息科技有限公司
// +----------------------------------------------------------------------
// | Copyright (c) 2013-2015 http://www.shelpe.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: LeePuman <ysyzlsm@163.com>
// +----------------------------------------------------------------------

/**
 *<strong>客户端和Services服务</strong>淘宝API请求统一入口. 所有API都可以通过ApiModel::request()来执行.
 * 示例见: ApiModel::getCateHotWords().
 * 
 * Web端的API请尽量使用taobaoAPI.class.php中的魔术方法__call()来执行. 示例:
 * <code>
 * $req["nick"] = $this->me['nick']; 
 * $this->_api->SimbaCampaignsGetRequest($req);
 * </code>
 * 其中$this是任意Model实例.
 * 
 * 该类会根据session自动判断用户, 并利用该用户的oauth. 如果没有提供session, 
 * 并且是在客户端执行, 则利用系统默认的用户来提交请求. 
 * 
 * 无论在网站上还是客户端, 都可以使用ThinkPHP自带的session()方法来设置用户
 * 从而利用用户自己的oauth.
 * 
 * @author jinxia
 *
 */
class Api2Model extends NewzanModel {
	
	protected $tableName = 'api_log';
	
	/** @var array */
	private $me = null;
	/** @var TopClient */
	private $topClient = null;
	/** @var bool Whether use Session to get access_token only */
	private $_useSessOnly = false;
	
	/**
	 * 构造函数
	 * @param string $useSessOnly 设为true的时候, 只从session中查找access_token, 以保证安全.\A
	 */
	public function __construct($useSessOnly = false) {
		$this->_useSessOnly = $useSessOnly;
		parent::__construct();
	}
	/**
	 * 初始化。检查session['me']和 session['me']['oauth']。
	 * 
	 * @see NewzanModel::_initialize()
	 */
	public function _initialize() {
		parent::_initialize();
	}
	
	/**
	 * 通用API执行方法. <strong>注意:</strong> 只有客户端和Services可调用该函数.
	 * 
	 * @param string $topClassName	淘宝API类名, 例: SimbaInsightToplevelcatsGetRequest.
	 * @param array $params API所有的参数列表. 所有的key对应于所给类的setXX()方法中的XX部分. 
	 * 例: array('nick'=>'猪猪妞妞', 'CategoryIds'=>'2333,93939839'). key的首字母可大写也可小写. 
	 * 后面部分必须驼峰命名, 跟setXX()中的XX一模一样.
	 * 
	 * @return json_decode过的值. 具体见 TopClient::execute()的返回值.
	 */
	public function request($topClassName, $params = null, $userId = 0) {
        //控制基于clientAction的__ACTION__的流量
        //self::checkClientApiQuota();
        //slow down the api request
		//$access_token = $this->me['oauth']['access_token']; //getUserAccessToken($this->me['user_id']);
		$oauth = null;
		$me = null;
		if(session('?manager')) {
			$manager = session('manager');
			$oauth = $manager['oauth'];
			$me = session('me');
		} else if(session('?me')) {
			$me = session('me');
			$oauth = $me['oauth'];
		} else if(!$this->_useSessOnly) {
			$userMdl = new UserModel();
			if( $userId ){
				$me = $userMdl->getUserByTaobaoId($userId);
				if(!$me) {
					throw_exception('给定的用户不存在');
				}
				if($me['authorized_to'] == 0) {
					$oauth = D('oauth')->where(array('taobao_user_id' => $userId))->find();
				} else {
					$oauth = D('oauth')->where(array('taobao_user_id' => $me['authorized_to']))->find();
				}				
			}else{
				$proxy_user = $userMdl->getAvailableUserForApi();
				$me = $proxy_user['user'];
				$oauth = $proxy_user['oauth'];				
			}

		}
		
		if(!$me) 
			throw_exception('ApiModel::request()没有给定用户');
		if(!$oauth) 
			throw_exception('ApiModel::request()无法获取oauth权限');
		
		$access_token = $oauth['access_token'];
		
		$c = $this->getTopClient();
		import('@.Library.TaobaoAPI.TopClient');
		$req = TopClient::init($topClassName);
		foreach($params as $key => $val) {
			$setMethod = 'set' . ucfirst($key);
			$req->$setMethod( (string) $val);
		}
		if((!isset($params['nick']) && !isset($params['Nick']))) {
			$req->putOtherTextParam('nick', $me['nick']); //UserModel::getSelfOrAuthUserNick()
		}
		return $c->execute($req, $access_token);
	}
	
	
	/**
	 * 查询的词的基础数据。
	 * 
	 * 利用词基础数据查询API: taobao.simba.insight.wordsbase.get 
	 * http://api.taobao.com/apidoc/api.htm?path=cid:46-apiId:10811
	 * 
	 * @param string|array(string) $words	需要取的词数组。词的个数为不能超过170个。
	 * @param string $timeSpan 		DAY|WEEK|MONTH|3MONTH中的任意一个。
	 * @param string $filter 				PV|CLICK|AVGCPC|COMPETITION|CTR的任意组合。
	 * @return An array of object。i_n_record_base的长度为$timeSpan的天数。
	 * 	       <strong>当查询的词语不存在的时候，对应的结果中就没有该词语的返回结果</strong>
	 * 			<strong>特别注意当所有词都不存在的时候，返回NULL</strong>
	 * [
	 * 		{
	 * 			"word" : XX,
	 * 			"in_record_base_list" : {"i_n_record_base" :  [ 
	 * 					{"avg_price": NN, "click": NN, "date": XX, ...}, // day 1
	 * 					{"avg_price": NN, "click": NN, "date": XX, ...}, // day 2
	 * 					......
	 * 					{"avg_price": NN, "click": NN, "date": XX, ...}, // day N。N为$timeSpan的天数。
	 * 			] }
	 * ]
	 * @throws API调用异常
	 */
	public function getWordProps($words, $timeSpan = 'WEEK', $filter = 'PV,CLICK,AVGCPC,COMPETITION,CTR') {
		if(is_array($words))
			$words = join(',', $words);
		if(empty($words))
			return null;
		try {
			$resp = $this->request('SimbaInsightWordsbaseGetRequest', array(
					'Words' => $words,
					'Time' => $timeSpan,
					'Filter' => $filter,
			));
		} catch (Exception $e) {
			if(getTopError($e->getMessage() == '字符串解析出错'))
				Log::record('ApiModel::getWordProps() error words: ' . $words . '.', Log::ERR, true);
			throw $e;
		}

        $resp = json_decode(json_encode($resp) , true);
		//正常返回
		if( isset($resp['in_word_bases'])  ){
			if( isset( $resp['in_word_bases']['i_n_word_base'] ) ){
				//有数据的词
				return $resp['in_word_bases']['i_n_word_base'];
			}else{
				//正常返回但是所有词语全部没有数据，确定为垃圾词
				return array();
			}
		//非正常返回
		}else{
			return null;
		}
	}
	
	/**
	 * 查询一大块词的基础属性。并将所有词的查询结果以一个数组返回。
	 * @see ApiModel::getWordProps()
	 * 
	 * @param string|array(string) $words	需要取的词数组。
	 * @param string $timeSpan 		DAY|WEEK|MONTH|3MONTH中的任意一个。
	 * @param string $fields 		PV|CLICK|AVGCPC|COMPETITION|CTR的任意组合。
	 * @param int $blockSize 		将大块分成的小块的大小。
	 * @return null|array(stdClass)	如果给定的词为空，则返回null，否则返回stdClass数组。具体格式见getBigWordProps()的返回值.
	 * @throws API调用异常
	 */
	public function getBigWordProps($words, $timeSpan = 'WEEK', $fields = 'PV,CLICK,AVGCPC,COMPETITION,CTR', $blockSize = 30) {
		if(empty($words))
			return null;
		
		$i = 0; 
		$n = count($words);
		$res = array();
		while($i * $blockSize < $n) {
			$blockWords = array_slice($words, $i * $blockSize, $blockSize); // TODO try catch exception
			$res += $this->getWordProps($blockWords, $timeSpan, $fields);
			
			++$i;
		}
		return $res;
	}
	
	/**
	 * 查询关键词的一种统计分析。
	 * 
	 * 利用类目分析数据查询API：taobao.simba.insight.catsanalysis.get
	 * http://api.taobao.com/apidoc/api.htm?path=cid:46-apiId:10808
	 *  
	 * @param string|array(string) $words	关键词数组。
	 * @param string $field 	AREA： 返回地域占比；SOURCE：返回来源占比；HPRICE：返回竞价分布。 
	 * 											只能是AREA、SOURCE或HPRICE中的一个。
	 * @return array(object)
	 * 【AREA】
	 * [
			{
			"word":"手机",
			"word_area_per":"{北京,657320,新疆,194771,重庆,243370,国外,85162,广东,1807092,天津,207922,浙江,918800,澳门,3070,广西,400739,宁夏,49891,江西,282730,台湾,16210,贵州,203452,安徽,382406,中国其它,35901,陕西,339827,辽宁,442148,山西,344170,青海,36657,香港,28004,内蒙,227664,四川,569409,江苏,1012583,河北,624793,西藏,9985,unknown,53,福建,472836,吉林,268626,上海,449823,海南,59397,湖北,415602,云南,238120,湖南,366162,甘肃,171037,山东,968324,河南,606151,黑龙江,376307}"
			},
			...
		]
	 * 【SOURCE】
	 * [
			{
			"word":"手机",
			"word_source_per":"{2,5891132,1,5559420}"
			},
			...
		]
	 * 【HPRICE】
	 *  [
			{
			"word":"手机",
			"word_hp_price":"{115,1,240,1,110,3,35,165,195,1,280,1,190,1,155,1,150,9,480,1,40,86,80,44,85,17,125,3,300,1,205,1,580,1,200,4,230,1,25,168,165,2,500,1,540,1,30,250,5,41,160,3,400,1,70,47,120,11,75,17,135,4,260,1,210,1,55,34,315,1,15,222,170,3,20,232,510,1,175,1,105,2,65,31,60,64,130,4,100,45,220,1,250,1,145,2,95,3,45,61,225,1,90,23,2300,1,180,3,10,559,990,1,185,1,490,1,4300,1,140,3,255,1,50,162}"
			},
			...
		]
	 * @throws API调用异常
	 */
	public function getWordAnalysis($words, $field) {
		if(is_array($words))
			$words =join(',', $words);
		if(empty($words))
			return null;

		$resp = $this->request('SimbaInsightWordsanalysisGetRequest', array(
				'Words' => $words,
				'Stu' => $field,
		));
		
		//如果返回正常应该有此对象属性
		if( isset($resp->in_word_analyses) ){
			if( isset( $resp->in_word_analyses->i_n_word_analysis ) ){
				return $resp->in_word_analyses->i_n_word_analysis;
			}else{
				return array();
			}
		}else{
		//返回异常
			return null;
		}
	}
	
	
	/**
	 * 查询词的相关词。
	 * 
	 * @param array(string) $words 要查询的词数组。最多100个。
	 * @return array(string) 第N十个词对应$words中的第N个。注意需要check返回值的个数，如果不是10×count($words)，
	 * 则表明至少有一个词没有10个相关词，结果不可用！
	 * 
	 * @throws API调用异常。
	 */
	public function getRelatedWords($words, $resNum = 10) {
		if(is_array($words))
			$words =join(',', $words);
		if(empty($words))
			return null;
		
		// $resp = $this->request('SimbaInsightCatsrelatedwordGetRequest', array(
		// 		'Words' => $words,
		// 		'ResultNum' => $resNum,
		// ));

		$resp = $this->request('SimbaInsightRelatedwordsGetRequest' , array(
				'bidwordList' => $words,
				'number' => $resNum
		));
		
		$words = $resp->related_words_result_list->insight_related_words;
		return $words ? $words : array();
		// return $resp['related_words']['string'] ? zh_arrtolower($resp['related_words']['string']) : array();
	}
	

	/**
	 * 批量将给定的关键词添加到给定的adgroup. <strong>最多100个关键词.</strong>
	 *
	 * 利用获取类目相关词API：taobao.simba.insight.keywords.add。
	 * API地址：http://api.taobao.com/apidoc/api.htm?path=cid:46-apiId:21681
	 *
	 * @param array(object) $keyword_prices
	 * @param int $adgroupId
	 * @return array2D 返回成功插入的关键词列表二位数组. 示例:
	 * [{
                "nick": "tbtest561",
                "campaign_id": 1489161932,
                "adgroup_id": 132443,
                "keyword_id": 135255,
                "word": "iphone",
                "max_price": 500,
                "is_default_price": false,
                "audit_status": "audit_pass",
                "audit_desc": "包含非法词",
                "is_garbage": false,
                "create_time": "2000-01-01 00:00:00",
                "modified_time": "2000-01-01 00:00:00"
            }]
	 * @throws API调用异常。
	 */
	public function addWordsSmall($keywordPrices, $adgroupId) {
		if(empty($keywordPrices))
			return array();
		
		$result = array();
		
		$filterdKeywordPrices = array();
		foreach($keywordPrices as $k){
			if(empty($k['word']) || empty($k['max_price']) || empty($k['matchscope'])) {
				throw_exception('ApiModel::addWords()词有问题: ' . $k['word'] . '^^' . $k['max_price'] . '^^' . $k['matchscope']);
			}
			//$filterdKeywordPrices[] = $k['word'] . '^^' . $k['max_price'] . '^^' . $k['matchscope'];
			$filterdKeywordPrices[] = array('word' => $k['word'], 'maxPrice' => round($k['max_price']), 'matchScope' => $k['matchscope']);
		}
		
		//$params = array('KeywordPrices' => implode(',', $filterdKeywordPrices));
		$params = array('KeywordPrices' => json_encode($filterdKeywordPrices));
		if($adgroupId > 0) {
			$params['AdgroupId'] = $adgroupId;
		}
		try {
			$resp = $this->request('SimbaKeywordsvonAddRequest', $params);
			$result = object_to_array($resp->keywords->keyword);
		} catch (Exception $e) {
			if(strpos($e->getMessage(), '302 Found') !== false) {
				$sliceSize = count($keywordPrices) / 2;
				$part1 = array_slice($keywordPrices, 0, $sliceSize);
				$part2 = array_slice($keywordPrices, $sliceSize);
				$result = array_merge(
						$this->addWords($part1, $adgroupId),
						$this->addWords($part2, $adgroupId)
				);
			} else {
				if(getTopError($e->getMessage()) == '关键词格式有误') {
					Log::record('添加的直通车关键词格式有误: ', Log::ERR, true);
					Log::record("AdgroupId: $adgroupId. Keywords: " . json_encode($filterdKeywordPrices), Log::ERR, true);
				}
				throw $e;
			}
		}
	
		return $result;
	}
	
	/**
	 * 单例TopClient.
	 * 
	 * @return TopClient
	 */
	private function getTopClient() {
		if($this->topClient === null) {
			import('@.Library.TaobaoAPI.TopClient');
			$this->topClient = new TopClient;
		}
		return $this->topClient;
	}
	
}
